package com.clp.util.math;

import com.clp.util.StringUtil;

import java.math.BigDecimal;
import java.util.Stack;

/**
 * 表达式计算工具
 */
public class ExpressionUtil {
    private static final ThreadLocal<Stack<Character>> opStackTl = new ThreadLocal<>();
    private static final ThreadLocal<Stack<BigDecimal>> valStackTl = new ThreadLocal<>();

    private static Stack<Character> getOpStack() {
        Stack<Character> stack = opStackTl.get();
        if (stack == null) {
            stack = new Stack<>();
            opStackTl.set(stack);
        }
        stack.clear();
        return stack;
    }

    private static Stack<BigDecimal> getValStack() {
        Stack<BigDecimal> stack = valStackTl.get();
        if (stack == null) {
            stack = new Stack<>();
            valStackTl.set(stack);
        }
        stack.clear();
        return stack;
    }

    /**
     * 运算
     *
     * @param expr 如：1 + (2 - 3) * 5 / 6
     * @return
     */
    public static BigDecimal calculate(String expr) {
        try {
            Stack<Character> opStack = getOpStack();
            Stack<BigDecimal> valStack = getValStack();

            char[] chars = expr.toCharArray();
            StringBuilder sb = null;
            for (char c : chars) {
                if (Character.isWhitespace(c)) continue;
                if (Character.isDigit(c) || c == StringUtil.C_DOT) { // 如果是数字或者小数点，则拼接
                    if (sb == null) {
                        sb = new StringBuilder("" + c);
                    } else {
                        sb.append(c);
                    }
                } else if (c == StringUtil.C_LEFT_PARENTHESIS) { // 是左括号
                    opStack.push(c);
                } else if (c == StringUtil.C_RIGHT_PARENTHESIS) { // 是右括号
                    if (sb != null) {
                        // 把前面的值压入值的栈
                        valStack.push(new BigDecimal(sb.toString()));
                        sb = null;
                    }
                    while (opStack.peek() != StringUtil.C_LEFT_PARENTHESIS) {
                        BinOp op = BinOp.gain(opStack.pop());
                        BigDecimal rightVal = valStack.pop();
                        BigDecimal leftVal = valStack.pop();
                        valStack.push(op.calculate(leftVal, rightVal));
                    }
                    opStack.pop();
                } else if (BinOp.isBinOp(c)) { // 是运算符
                    if (sb != null) {
                        // 把前面的值压入值的栈
                        valStack.push(new BigDecimal(sb.toString()));
                        sb = null;
                    }
                    // 判断运算符栈栈顶的运算符与当前运算符的优先级
                    if (opStack.isEmpty() || opStack.peek() == StringUtil.C_LEFT_PARENTHESIS ||
                            BinOp.gain(opStack.peek()).getPriority() < BinOp.gain(c).getPriority()) {
                        // 如果栈空、栈顶为左括号、栈顶运算符优先级更小，则当前运算符入栈
                        opStack.push(c);
                    } else {
                        do {
                            // 如果栈顶运算符优先级更大，则需要处理它
                            BinOp op = BinOp.gain(opStack.pop());
                            BigDecimal rightVal = valStack.pop();
                            BigDecimal leftVal = valStack.pop();
                            valStack.push(op.calculate(leftVal, rightVal));
                        } while (!opStack.isEmpty() && opStack.peek() != StringUtil.C_LEFT_PARENTHESIS &&
                                BinOp.gain(opStack.peek()).getPriority() >= BinOp.gain(c).getPriority());
                        opStack.push(c);
                    }
                } else {
                    throw new IllegalArgumentException("非法字符：" + c);
                }
            }
            while (!opStack.isEmpty()) {
                if (sb != null) {
                    valStack.push(new BigDecimal(sb.toString()));
                    sb = null;
                }
                BinOp op = BinOp.gain(opStack.pop());
                BigDecimal rightVal = valStack.pop();
                BigDecimal leftVal = valStack.pop();
                valStack.push(op.calculate(leftVal, rightVal));
            }
            return valStack.pop();
        } catch (ArithmeticException ae) {
            throw ae;
        } catch (Exception e) {
            throw new IllegalArgumentException("非法表达式：" + expr, e);
        }
    }
}
